/*  Copyright © 2009 Blake Hegerle -*- C++ -*- */

/*  This file is part of metal.
 
    Metal is free software: you can redistribute it and/or modify it
    under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.
    
    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    General Public License for more details.
 
    You should have received a copy of the GNU General Public License
    along with this program.  If not, see
    <http://www.gnu.org/licenses/>.

*/

#include "identifier.h"
#include "module.h"
#include "drivers/idriver.h"
#include "drivers/llvm_driver.h"
#include "drivers/llvm_program.h"
#include "io.h"

#include <iostream>
#include <cassert>

using namespace metal;
using namespace metal::driver;
using namespace std;

namespace {
  ident(a);
  ident(b);
  ident(c);
  ident(i);
  ident(return_bool);
  ident(return_character);
  ident(return_u8);
  ident(return_i8);
  ident(return_u16);
  ident(return_i16);
  ident(return_u32);
  ident(return_i32);
  ident(return_u64);
  ident(return_i64);
  ident(return_f32);
  ident(return_f64);
  ident(return_1st_param);
  ident(return_2nd_param);
  ident(u8_complement);
  ident(i8_complement);
  ident(u16_complement);
  ident(i16_complement);
  ident(u32_complement);
  ident(i32_complement);
  ident(u64_complement);
  ident(i64_complement);

  ident(i8_negate);
  ident(f32_negate);

  ident(bool_not);

  ident(cast_i8tof32);
  ident(cast_f32toi32);

  ident(add_i8i8);
  ident(eq_i32i32);
  ident(lt_i32i32);
  ident(gt_f32f32);

  ident(min_i32i32);

  ident(eval_x);

  ident(array_3xi32);
  ident(subscript_3xi32);
  ident(subscript2_3x4xi32);

  ident(simple_map);
  ident(map3);

  ident(even_filter);
  ident(filter_only);
  ident(filter_and_eval);
}

#define mod(x, ...) auto x = ( module<>(#x), __VA_ARGS__);

#define check(...) cout << #__VA_ARGS__ << endl; chk(__VA_ARGS__);

template<typename T, typename... Args>
void chk(T (*func)(Args...), T val, Args... args) {
  cout << "function returning " << typeid(T).name() << endl;

  typedef double l;
  cout << "\t" << (l)func(args...) << " == " << (l)val << endl;
  assert(func(args...) == val);
}

void driver_tests() {
  auto d = llvm_driver();
  // cnst_impl(1)->emit(d);

  cout << "emission ok" << endl;

  using namespace std;

  mod(mod1,

      // return_bool = def() ( (bool)true ),
      return_character = def() ( 'a' ),
      return_i8 = def() ( (signed char)1 ),
      return_u8 = def() ( (unsigned char)2 ),
      return_i16 = def() ( (short)3 ),
      return_u16 = def() ( (unsigned short)4 ),
      return_i32 = def() ( (int)5 ),
      return_u32 = def() ( (unsigned)6 ),
      return_i64 = def() ( (long long)7 ),
      return_u64 = def() ( (unsigned long long)8 ),
      return_f32 = def() ( 1.0f ),
      return_f64 = def() ( 2.0 ),
      return_2nd_param = def(param(int, a), param(int, b)) ( b ),
      return_1st_param = def(param(int, a)) ( return_2nd_param(a, a) ),
      u8_complement = def(param(unsigned char, a) ) ( ~a ),
      i8_complement = def(param(signed char, a) ) ( ~a ),
      u16_complement = def(param(unsigned short, a) ) ( ~a ),
      i16_complement = def(param(short, a) ) ( ~a ),
      u32_complement = def(param(unsigned, a) ) ( ~a ),
      i32_complement = def(param(int, a) ) ( ~a ),
      u64_complement = def(param(unsigned long long, a)) 
      ( ~a ),
      i64_complement = def(param(long long, a) ) ( ~a ),
      i8_negate = def(param(signed char, a) ) ( -a ),
      f32_negate = def(param(float, a) ) ( -a ),

      cast_i8tof32 = def(param(signed char, a)) ( cast<float>(a) ),
      cast_f32toi32 = def(param(float, a)) ( cast<int>(a) ),
      add_i8i8 = def(param(signed char, a), 
		       param(signed char, b)) ( a+b ),
      eq_i32i32 = def(param(int, a), param(int, b)) ( a == b ),
      lt_i32i32 = def(param(int, a), param(int, b)) ( a < b ),
      gt_f32f32 = def(param(float, a), param(float, b)) ( a > b),
      
      min_i32i32 = def(param(int, a), param(int, b)) ( cond(a < b, a, b) ),
      
      eval_x = def() ( eval(a, a = 3) ),
      
      array_3xi32 = def() ( array(1, 2, 3) ),
      
      subscript_3xi32 = def() ( array(1, 2, 3)[1u] ),
      
      subscript2_3x4xi32 = def() ( array(array(1, 2, 3, 4), 
						array(5, 6, 7, 8),
						array(9,10,11,12))[1][2] ),

      simple_map = def() ( metal::map(a*2, a *= array(1, 2, 3))[2] ),

      map3 = def(param(int, i)) ( metal::map(a*b*c, a *= array(2, 3, 5), b *= array(7, 11, 13), c *= array(17, 19, 23))[i] ),
      
      even_filter = def(param(int, i)) ( filter(a, a *= array(1, 2, 3, 4), (a % 2) == 0)[i] ),
      
      filter_only = def(param(int, i)) ( filter(a *= array(0, 1, 2), a > 0)[i] ),

      filter_and_eval = def(param(int, i)) 
      ( filter(a + 1, a *= array(-1, 0, 1), a > 0)[i] )
      
      );
  
  mod1.emit(d);
  d.dump();

  auto program = link(mod1);
  
  // check(program.call(return_bool), (bool)true);
  check(program.call(return_character), 'a');
  check(program.call(return_i8), (signed char)1);
  check(program.call(return_u8), (unsigned char)2);
  check(program.call(return_i16), (short)3);
  check(program.call(return_u16), (unsigned short)4);
  check(program.call(return_i32), (int)5);
  check(program.call(return_u32), (unsigned)6);
  check(program.call(return_i64), 7ll);
  check(program.call(return_u64), 8ull);
  check(program.call(return_f32), 1.0f);
  check(program.call(return_f64), 2.0);
  check(program.call(return_1st_param), 2, 2);
  check(program.call(return_2nd_param), 1, 0, 1);
  check(program.call(u8_complement), (unsigned char)0xf0, (unsigned char)0x0f);
  check(program.call(i8_complement), (signed char)0xf0, (signed char)0x0f);
  check(program.call(u16_complement), 
	(unsigned short)0xf00f, 
	(unsigned short)0x0ff0);
  check(program.call(i16_complement), 
	(short)0xf00f, 
	(short)0x0ff0);
  check(program.call(u32_complement), 
	(unsigned)0xf0f00f0f,
	(unsigned)0x0f0ff0f0);
  check(program.call(i32_complement), 
	(int)0xf0f00f0f, 
	(int)0x0f0ff0f0);
  check(program.call(u64_complement), 
	(unsigned long long)0xf0f0f0f00f0f0f0full,
	(unsigned long long)0x0f0f0f0ff0f0f0f0ull);
  check(program.call(i64_complement), 
	(long long)0x0f0f0f0ff0f0f0f0ull, 
	(long long)0xf0f0f0f00f0f0f0full);
  check(program.call(i8_negate),
	(signed char)-2,
	(signed char)2);
  check(program.call(f32_negate),
	(float)-3.25f,
	(float)3.25f);

  check(program.call(cast_i8tof32), 3.0f, (signed char)3);
  check(program.call(cast_f32toi32), 4, 4.1f);

  check(program.call(add_i8i8), 
		     (signed char)2, 
		     (signed char)1, 
		     (signed char)1);

  check(program.call(eq_i32i32), (bool)1, 2, 2);
  check(program.call(lt_i32i32), (bool)1, 2, 3);
  check(program.call(gt_f32f32), (bool)1, 3.0f, 2.1f);

  check(program.call(min_i32i32), 3, 3, 4);
  check(program.call(min_i32i32), 3, 4, 3);

  check(program.call(subscript_3xi32), 2);

  check(program.call(subscript2_3x4xi32), 7);

  check(program.call(simple_map), 6);

  check(program.call(map3), 2*7*17, 0);
  check(program.call(map3), 3*11*19, 1);
  check(program.call(map3), 5*13*23, 2);

  cout << "event_filter(0) = " <<  program.call(even_filter)(0) << endl;
  cout << "event_filter(1) = " <<  program.call(even_filter)(1) << endl;
  cout << "event_filter(2) = " <<  program.call(even_filter)(2) << endl;
  cout << "event_filter(3) = " <<  program.call(even_filter)(3) << endl;

  check(program.call(even_filter), 2, 0);
  check(program.call(even_filter), 4, 1);

  check(program.call(filter_only), 1, 0);
  check(program.call(filter_only), 2, 1);

  check(program.call(filter_and_eval), 2, 0);

  cout << "driver_tests ok" << endl;
}
